
package com.zst.http.forward.remote;

import com.zst.http.forward.utils.InetSocketAddressResolver;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.text.MessageFormat;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 注意一下性能表现，看看会不会频繁的创建对象
 *
 * 注意，这种方式有个严重的缺陷，如果当前服务与目标服务之间一个用HTTP一个用HTTPS的话，会因为数据报文内容不一致而出现错误，还是放弃了
 * @deprecated
 */
@Slf4j
public class NativeByteForwardHandler implements ForwardHandler {
    private Supplier<String> remoteHostSupplier;
    private Bootstrap bootstrap;
    private EventLoopGroup eventLoopGroup;
    private RemoteResponseHandler remoteResponseHandler;
    private final Map<String, CompletableFuture<FullHttpResponse>> pendingRequests
            = new ConcurrentHashMap<>(1024);

    public NativeByteForwardHandler(Supplier<String> remoteHostSupplier) {
        if (remoteHostSupplier == null) {
            throw new IllegalArgumentException();
        }
        this.remoteHostSupplier = remoteHostSupplier;

        remoteResponseHandler = new RemoteResponseHandler();

        init();
    }

    @Override
    @SneakyThrows
    public CompletableFuture<FullHttpResponse> doForward(FullHttpRequest request) {
        String host;
        try {
            host = remoteHostSupplier.get();
        } catch (Exception e) {
            throw new RuntimeException("获取转发目标host失败", e);
        }
        if (host == null) {
            throw new IllegalArgumentException("转发目标host为空");
        }

        InetSocketAddress remoteAddress = InetSocketAddressResolver.resolveFromURL(host);
        if (remoteAddress == null) {
            throw new IllegalArgumentException("转发目标host解析失败");
        }
        log.debug("目标请求地址:{}", remoteAddress.toString());

        ChannelFuture bindFuture = bootstrap.connect(remoteAddress);
        if (!bindFuture.await(5, TimeUnit.SECONDS)) {
            throw new RuntimeException("连接转发目标host超时");
        }

        Channel remoteChannel = bindFuture.sync().channel();
        log.debug("连接转发目标host成功, channel:{}", remoteChannel.toString());

        CompletableFuture<FullHttpResponse> future = new CompletableFuture<>();
        pendingRequests.put(remoteChannel.id().asLongText(), future);

        remoteChannel.pipeline().writeAndFlush(rewriteRequest(request)).sync();
        log.debug("channel:{}, 请求已发送, 等待响应", remoteChannel.id().asLongText());

        return future;
    }

    public void close() {
        if (eventLoopGroup != null && !eventLoopGroup.isShutdown()) {
            eventLoopGroup.shutdownGracefully();
            eventLoopGroup = null;
        }
    }

    private void init() {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() / 2);
        bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .handler(new ClientInitializer());
    }

    private FullHttpRequest rewriteRequest(FullHttpRequest request) {
        String uri = request.uri();
        String newUri = remoteHostSupplier.get() + uri;

        FullHttpRequest newRequest = request.retainedDuplicate();
        newRequest.setUri(newUri);
        return newRequest;
    }

    private class ClientInitializer extends ChannelInitializer<Channel> {
        @Override
        protected void initChannel(Channel ch) throws Exception {
            ch.pipeline().addLast(new HttpRequestEncoder())
                    .addLast(new HttpResponseDecoder())
                    .addLast(new HttpObjectAggregator(4 * 1024 * 1024))
                    .addLast(remoteResponseHandler);
        }
    }

    @ChannelHandler.Sharable
    private class RemoteResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
        private final Logger log = LoggerFactory.getLogger(RemoteResponseHandler.class);

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception {
            String channelId = ctx.channel().id().asLongText();
            CompletableFuture<FullHttpResponse> future = pendingRequests.remove(channelId);
            if (future != null) {
                log.debug("channelId:{}, 接收到http response:{}", channelId, msg.toString());
                future.complete(msg);
            } else {
                log.warn("channelId:{}, 接收到无效的http response", channelId);
            }

            ctx.channel().close();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            log.error(MessageFormat.format("处理转发请求的响应数据时发生错误, {0}", ctx.channel().toString()), cause);

            String channelId = ctx.channel().id().asLongText();
            CompletableFuture<FullHttpResponse> future = pendingRequests.remove(channelId);
            if (future != null) {
                future.completeExceptionally(new RuntimeException("处理转发请求的响应数据时发生错误", cause));
            } else {
                log.warn("channelId:{}, 接收到无效的http response", channelId);
            }

            ctx.channel().close();
        }
    }
}
